Skip to content

feat(idlc): --cyclone defaults un-annotated aggregates to @final - #29

Merged
SandraK82 merged 4 commits into
mainfrom
feat/cyclone-default-final
Aug 1, 2026
Merged

feat(idlc): --cyclone defaults un-annotated aggregates to @final#29
SandraK82 merged 4 commits into
mainfrom
feat/cyclone-default-final

Conversation

@SandraK82

Copy link
Copy Markdown
Contributor

Related to #27.

CycloneDDS treats an un-annotated aggregate as @final; ZeroDDS follows the XTypes 1.3 default of @appendable. Under XCDR1 this is invisible — an @appendable type emits no DHEADER, so the bytes match @final and everything interoperates. Under XCDR2 the @appendable reader expects a DHEADER the @final writer never wrote, so every sample fails to decode. The endpoints still match (cross-vendor matching falls back to the type name; complete standard TypeObject validation is not reached here), so it presents as "matched but no data".

Changes

  • --cyclone now defaults un-annotated aggregates to @final (matching CycloneDDS' own default). Previously the flag was accepted but a codegen no-op. A reader generated with --cyclone now interoperates with a CycloneDDS @final/XCDR2 writer out of the box.
  • take() surfaces the framing mismatch as a WireError with decode diagnostics instead of a generic error, so the failure is visible rather than dropped on the floor.
  • Gated live interop regression matrix (interop/cyclone-xtypes-27/) covering the default XCDR1 path, the forced-XCDR2 failure, and the --cyclone fix, against CycloneDDS 11.0.1.

Validation (5-case matrix, CycloneDDS 11.0.1)

Writer (Cyclone) Reader (ZeroDDS) Expected Result
final, XCDR1 @appendable (default) samples 39 samples
@appendable, XCDR2 @appendable samples 40 samples
final, XCDR2 @appendable no samples (decode error) 0 samples, 40 errors
ZeroDDS @appendable/XCDR2 Cyclone samples 40 samples
final, XCDR2 @Final (--cyclone) samples 40 samples, 0 errors

Workaround without this PR: annotate the type @final, or generate with --default-extensibility final.

This does not close #27: the reporter's default (XCDR1) path failure is not reproducible here and needs more data — leaving the issue open for that.

--cyclone was accepted but a codegen no-op, so ZeroDDS still generated
@appendable for un-annotated types while CycloneDDS defaults them to
@Final. The two then disagree on XCDR2 framing (DHEADER present vs not),
and a forced-XCDR2 reader fails to decode the peer's samples (#27).

--cyclone now resolves the default extensibility to `final` for
un-annotated struct/union/enum, matching CycloneDDS' generator default.
Precedence (highest first): explicit IDL annotation > explicit
--default-extensibility > --cyclone (final) > global default (appendable).
Resolved once after argument parsing, so order is irrelevant; --opendds
stays a pure intent no-op. Help text and a precedence test added.
A gated, live DCPS-over-UDP interop harness (domain 100, topic `robot`)
pinning down the #27 finding and the --cyclone fix. Five cases, each
reporting match / decoded-sample / decode-error counts SEPARATELY:

  final+XCDR1       -> ZeroDDS @appendable  match, samples, 0 errors
  appendable+XCDR2  -> ZeroDDS @appendable  match, samples, 0 errors
  final+XCDR2       -> ZeroDDS @appendable  match, 0 samples, WireError (#27)
  reverse: ZeroDDS @appendable/XCDR2 -> Cyclone reader  samples
  final+XCDR2       -> ZeroDDS @Final (--cyclone)  match, samples, 0 errors

Case 3 shows the reporter's symptom is a SURFACED decode error (take() ->
WireError), not a silent drop; case 5 shows generating the reader with
`--cyclone` (default-final) fixes it. Opt-in / gated: loud-skips (exit 0)
without a Python that can import cyclonedds. Reference vendor CycloneDDS
11.0.1. Verified end-to-end on the Linux test host (all five PASS).

The reader lives in a standalone crate (own [workspace]); its src/robot.rs
is regenerated per case by run_matrix.sh (git-ignored).
The comments on `reader_accept_repr` and its regression test claimed the
XTypes 1.3 §7.6.2 default (empty) DataRepresentation policy "accepts both
XCDR1 and XCDR2". The spec default is XCDR1 only. Advertising both is
ZeroDDS' own deliberate interop choice (so an XCDR1-defaulting CycloneDDS/
RTI/OpenDDS writer still matches), not the meaning of the spec default.
Comment-only; the advertise-both behaviour is unchanged.
)

A decode failure at take() now reports the received encapsulation
(representation + byte order) and this reader type's own extensibility,
and names an extensibility/framing mismatch as a *plausible* cause: in
XCDR2 @appendable/@mutable carry a DHEADER length prefix and @Final does
not, so a peer with a different extensibility fails to decode. Flagged as
plausible, not asserted — the remote type is not available at the decode
site to confirm it. Applied at the five representation-dispatched
decode_for_encap call sites via a shared decode_wire_error helper. Test
added; the advertise/decode behaviour is otherwise unchanged.
@SandraK82
SandraK82 merged commit a30ae50 into main Aug 1, 2026
10 checks passed
SandraK82 added a commit that referenced this pull request Aug 3, 2026
…#28)

The all-core Fast DDS source build (XTypes/TypeObject TUs are memory-heavy)
peaked past the hosted-runner limit and the runner was SIGTERM'd twice at
~54% (exit 143) on the unchanged SHA — a resource limit, not transient infra.
Cap the build to 2 jobs (CMAKE_BUILD_PARALLEL_LEVEL + gradle --max-workers)
so the peak fits, add actions/cache keyed on the pinned versions so the stack
builds once, and set a 60-minute job timeout for a clean failure mode.
Cyclone cell is already green (both directions + the #29 XTypes cases).
@SandraK82
SandraK82 deleted the feat/cyclone-default-final branch August 3, 2026 20:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

help(interop): interoperability with CycloneDDS

1 participant